﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.IO.Ports;
using System.Net.Sockets;
using System.Threading;

namespace 读取西门子PLC_Demo
{
    public static partial class MethodExt
    {
        /// <summary>
        /// 安全关闭;  没完全理解到,感觉不完善
        /// </summary>
        /// <param name="socket"></param>
        public static Result SafeClose(this Socket socket)
        {
            Result result = new Result();
            try
            {
                if (socket?.Connected ?? false) socket?.Shutdown(SocketShutdown.Both);//正常关闭连接
            }
            catch (Exception e)
            {
                result.IsSucceed = false;
                result.Exception = e;
                return result;
            }

            try
            {
                socket?.Close();
            }
            catch (Exception e)
            {
                result.IsSucceed = false;
                result.Exception = e;
                return result;
            }
            return result;
        }

        #region 字节操作

        /// <summary>
        /// 获得直接数组中的某一位;取字节中的第一位,索引应该是0而不是1
        /// </summary>
        /// <param name="bytes"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public static bool GetBit(this byte[] bytes, int index)
        {
            if (index >= 0 && index < bytes.Length * 8)
            {
                var n = index / 8;
                var m = index % 8;
                return bytes[n].GetBit(m);
            }
            throw new Exception("索引必须大于0且小于这个字节数组的位长度");//直接返回false？
        }

        /// <summary>
        /// 获得字节中的某一位,取字节中的第一位,索引应该是0而不是1
        /// </summary>
        /// <param name="bytes"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public static bool GetBit(this byte bytes, int index)
        {
            if (index >= 0 && index <= 7)
            {
                var t = Math.Pow(2.0, index);
                var res = (bytes & (int)Math.Pow(2.0, index)) != 0;
                return res;
            }
            throw new Exception("索引必须为0-7之间");//直接返回false？
        }

        /// <summary>
        /// 给一个字节中某一位赋值
        /// </summary>
        /// <param name="value"></param>
        /// <param name="bit"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        public static byte SetBit(this byte value, int bit, bool val)
        {
            return val ? ((byte)(value | (byte)Math.Pow(2.0, bit))) : ((byte)(value & (byte)(~(byte)Math.Pow(2.0, bit))));
        }

        /// <summary>
        /// 字节格式转换
        /// </summary>
        /// <param name="value"></param>
        /// <param name="format"></param>
        /// <param name="reverse">是否经过了反转</param>
        /// <returns></returns>
        public static byte[] Format(this byte[] value, EndianFormat format = EndianFormat.ABCD, bool reverse = true)
        {
            if (!reverse)
            {
                switch (format)
                {
                    case EndianFormat.ABCD:
                        format = EndianFormat.DCBA;
                        break;

                    case EndianFormat.BADC:
                        format = EndianFormat.CDAB;
                        break;

                    case EndianFormat.CDAB:
                        format = EndianFormat.BADC;
                        break;

                    case EndianFormat.DCBA:
                        format = EndianFormat.ABCD;
                        break;
                }
            }
            byte[] buffer = value;
            if (value.Length == 2)
            {
                buffer = new byte[2];
                switch (format)
                {
                    case EndianFormat.ABCD:
                    case EndianFormat.BADC:
                        buffer[0] = value[0];
                        buffer[1] = value[1];
                        break;

                    case EndianFormat.CDAB:
                    case EndianFormat.DCBA:
                        buffer[0] = value[1];
                        buffer[1] = value[0];
                        break;
                }
            }
            else if (value.Length == 4)
            {
                buffer = new byte[4];
                switch (format)
                {
                    case EndianFormat.ABCD:
                        buffer[0] = value[0];
                        buffer[1] = value[1];
                        buffer[2] = value[2];
                        buffer[3] = value[3];
                        break;

                    case EndianFormat.CDAB:
                        buffer[0] = value[2];
                        buffer[1] = value[3];
                        buffer[2] = value[0];
                        buffer[3] = value[1];
                        break;

                    case EndianFormat.BADC:
                        buffer[0] = value[1];
                        buffer[1] = value[0];
                        buffer[2] = value[3];
                        buffer[3] = value[2];
                        break;

                    case EndianFormat.DCBA:
                        buffer[0] = value[3];
                        buffer[1] = value[2];
                        buffer[2] = value[1];
                        buffer[3] = value[0];
                        break;
                }
            }
            else if (value.Length == 8)
            {
                buffer = new byte[8];
                switch (format)
                {
                    case EndianFormat.ABCD:
                        buffer[0] = value[0];
                        buffer[1] = value[1];
                        buffer[2] = value[2];
                        buffer[3] = value[3];
                        buffer[4] = value[4];
                        buffer[5] = value[5];
                        buffer[6] = value[6];
                        buffer[7] = value[7];
                        break;

                    case EndianFormat.BADC://ABCDEFGH  -- BA DC
                        buffer[0] = value[1];
                        buffer[1] = value[0];
                        buffer[2] = value[3];
                        buffer[3] = value[2];
                        buffer[4] = value[5];
                        buffer[5] = value[4];
                        buffer[6] = value[7];
                        buffer[7] = value[6];
                        break;

                    case EndianFormat.CDAB://ABCDEFGH   --   CD AB
                        buffer[0] = value[6];
                        buffer[1] = value[7];
                        buffer[2] = value[4];
                        buffer[3] = value[5];
                        buffer[4] = value[2];
                        buffer[5] = value[3];
                        buffer[6] = value[0];
                        buffer[7] = value[1];
                        break;

                    case EndianFormat.DCBA:
                        buffer[0] = value[7];
                        buffer[1] = value[6];
                        buffer[2] = value[5];
                        buffer[3] = value[4];
                        buffer[4] = value[3];
                        buffer[5] = value[2];
                        buffer[6] = value[1];
                        buffer[7] = value[0];
                        break;
                }
            }
            else if (format == EndianFormat.BADC)
            {
                buffer = new byte[value.Length];
                for (int i = 1; i < value.Length + 1; i++)
                {
                    if (i % 2 == 0)
                    {
                        buffer[i - 1] = value[i - 2];
                    }
                    else
                    {
                        buffer[i - 1] = value[i];
                    }
                }
            }
            return buffer;
        }

        public static bool[] ToBits(this byte b)
        {
            bool[] array = new bool[8];
            for (int i = 0; i <= 7; i++)
            {
                array[i] = (b & 1) == 1;
                b = (byte)(b >> 1);
            }
            return array;
        }

        /// <summary>
        /// 转化为字节,根据format
        /// </summary>
        /// <param name="value"></param>
        /// <param name="dataType"></param>
        /// <param name="format"></param>
        /// <returns></returns>
        public static byte[] ToBytes(this double value, DataTypeEnum dataType, EndianFormat format = EndianFormat.DCBA)
        {
            byte[] bytess = null;
            switch (dataType)
            {
                case DataTypeEnum.None:
                    throw new ArgumentException("没有实现的字节");
                case DataTypeEnum.Bool:
                    bytess = BitConverter.GetBytes(Convert.ToBoolean(value)).Format(format);
                    break;

                case DataTypeEnum.Byte:
                    bytess = new byte[] { Convert.ToByte(value) }.Format(format);
                    break;

                case DataTypeEnum.Int16:
                    bytess = BitConverter.GetBytes(Convert.ToInt16(value)).Format(format);
                    break;

                case DataTypeEnum.UInt16:
                    bytess = BitConverter.GetBytes(Convert.ToUInt16(value)).Format(format);
                    break;

                case DataTypeEnum.Int32:
                    bytess = BitConverter.GetBytes(Convert.ToInt32(value)).Format(format);
                    break;

                case DataTypeEnum.UInt32:
                    bytess = BitConverter.GetBytes(Convert.ToUInt32(value)).Format(format);
                    break;

                case DataTypeEnum.Int64:
                    bytess = BitConverter.GetBytes(Convert.ToInt64(value)).Format(format);
                    break;

                case DataTypeEnum.UInt64:
                    bytess = BitConverter.GetBytes(Convert.ToUInt64(value)).Format(format);
                    break;

                case DataTypeEnum.Float:
                    bytess = BitConverter.GetBytes(Convert.ToSingle(value)).Format(format);
                    break;

                case DataTypeEnum.Double:
                    bytess = BitConverter.GetBytes(Convert.ToDouble(value)).Format(format);
                    break;

                case DataTypeEnum.String://todo
                    bytess = BitConverter.GetBytes(Convert.ToUInt16(value)).Format(format);
                    break;

                case DataTypeEnum.Wstring:
                    bytess = BitConverter.GetBytes(Convert.ToUInt16(value)).Format(format);
                    break;

                default:
                    bytess = BitConverter.GetBytes(Convert.ToUInt16(value)).Format(format);
                    break;
            }
            return bytess;
        }

        #endregion 字节操作

        #region Common

        /// <summary>
        /// 去掉所有空格,并统一为大写或者小写(默认大写)
        /// </summary>
        /// <param name="str"></param>
        /// <param name="Case">转化为大写还是小写,默认大写</param>
        /// <returns></returns>
        public static string Norm(this string str, bool Case = true)
        {
            if (Case)
            {
                return str.Replace(" ", "").ToUpper();
            }
            else
            {
                return str.Replace(" ", "").ToLower();
            }
        }

        public static Result<byte[]> Read(this Socket socket, int receiveCount, int BufferSize = 4096)
        {
            var result = new Result<byte[]>();
            if (receiveCount < 0)
            {
                result.IsSucceed = false;
                result.Err = $"读取长度[receiveCount]为{receiveCount}";
                result.AddErr2List();
                return result;
            }
            byte[] receiveBytes = new byte[receiveCount];
            int receiveFinish = 0;
            while (receiveFinish < receiveCount)
            {
                // 分批读取
                int receiveLength = (receiveCount - receiveFinish) >= BufferSize ? BufferSize : (receiveCount - receiveFinish);
                var readLeng = 1;
                try
                {
                    readLeng = socket.Receive(receiveBytes, receiveFinish, receiveLength, SocketFlags.None);
                    if (readLeng == 0)
                    {
                        socket?.SafeClose();
                        result.IsSucceed = false;
                        result.Err = $"连接被断开";
                        result.AddErr2List();
                        return result;
                    }
                    receiveFinish += readLeng;
                }
                catch (SocketException ex)
                {
                    var xxx = readLeng;
                    try
                    {
                        socket?.SafeClose();
                    }
                    catch (Exception e)
                    {
                        result.Err = $"断开Socket错误：{e.Message}";
                    }

                    if (ex.SocketErrorCode == SocketError.TimedOut)
                    {
                        result.Err = $"连接超时：{ex.Message}";
                    }
                    else
                    {
                        result.Err = $"连接被断开，{ex.Message}";
                    }
                    result.IsSucceed = false;
                    result.AddErr2List();
                    result.Exception = ex;
                    return result;
                }
            }
            result.Value = receiveBytes;
            return result.EndTime();
        }

        /// <summary>
        /// 封装读取方法(确认读取完数据才返回  或者超时返回)
        /// </summary>
        /// <param name="serialPort"></param>
        /// <returns></returns>
        public static Result<byte[]> Read(this SerialPort serialPort)
        {
            Result<byte[]> result = new Result<byte[]>();
            DateTime beginTime = DateTime.Now;
            var tempBufferLength = serialPort.BytesToRead;
            //在(没有取到数据或BytesToRead在继续读取)且没有超时的情况，延时处理
            while ((serialPort.BytesToRead == 0 || tempBufferLength != serialPort.BytesToRead) && DateTime.Now - beginTime <= TimeSpan.FromMilliseconds(serialPort.ReadTimeout))
            {
                tempBufferLength = serialPort.BytesToRead;
                //延时处理
                Thread.Sleep(20);
            }
            byte[] buffer = new byte[serialPort.BytesToRead];
            var receiveFinish = 0;
            while (receiveFinish < buffer.Length)
            {
                var readLeng = serialPort.Read(buffer, receiveFinish, buffer.Length);
                if (readLeng == 0)
                {
                    result.Value = null;
                    return result.EndTime();
                }
                receiveFinish += readLeng;
            }
            result.Value = buffer;
            return result.EndTime();
        }

        #endregion Common

        /// <summary>
        /// 去重
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TKey"></typeparam>
        /// <param name="source"></param>
        /// <param name="keySelector"></param>
        /// <returns></returns>
        public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
        {
            HashSet<TKey> seenKeys = new HashSet<TKey>();
            foreach (TSource element in source)
            {
                if (seenKeys.Add(keySelector(element)))
                {
                    yield return element;
                }
            }
        }

        public static int GetBytelength(DataTypeEnum type)
        {
            switch (type)
            {
                case DataTypeEnum.None:
                    throw new System.Exception("未被定义的数据类型");
                case DataTypeEnum.Bool:
                    return 1;

                case DataTypeEnum.Byte:
                    return 1;

                case DataTypeEnum.Int16:
                    return 2;

                case DataTypeEnum.UInt16:
                    return 2;

                case DataTypeEnum.Int32:
                    return 4;

                case DataTypeEnum.UInt32:
                    return 4;

                case DataTypeEnum.Int64:
                    return 8;

                case DataTypeEnum.UInt64:
                    return 8;

                case DataTypeEnum.Float:
                    return 4;

                case DataTypeEnum.Double:
                    return 8;

                case DataTypeEnum.String:
                    throw new System.Exception("数据类型未实现");

                case DataTypeEnum.Wstring:
                    throw new System.Exception("数据类型未实现");

                case DataTypeEnum.Custom:
                    throw new System.Exception("数据类型未实现");
                default:
                    return 2;
            }
        }
    }
}